Skip to content

14 函数基本概念与实践

  • 函数(functions)是 Go 语言最基本的构建块之一,用于封装一段可以复用的代码。
  • 函数可以接受输入参数并返回结果,可以有返回值,也可以没有返回值。
  • Go 的函数设计灵活,支持多返回值、匿名函数、函数作为参数传递等高级特性。
特性说明
基本函数定义使用 func 关键字定义,支持参数和返回值。
多返回值Go 支持返回多个值,常用于处理错误。
匿名函数没有名字的函数,支持赋值给变量或作为闭包使用。
可变参数使用 ... 定义可变参数,允许传递不定数量的参数。
闭包函数引用外部变量,形成闭包,保持对外部变量的访问。
递归函数函数可以调用自身,形成递归。
defer延迟执行,常用于资源清理。
函数作为参数函数可以作为参数传递给其他函数,实现更灵活的代码结构。
方法Go 中的方法是带有接收者的函数,附属于特定类型。

函数的定义

go
func functionName(parameter1 type1, parameter2 type2) returnTypeList {
    // 函数体
    // 可以包含多条语句
    return result
}
  • func:关键字,表示这是一个函数。
  • functionName:函数名,标识这个函数的名称(如果是匿名函数,这里不需要名字)。函数名应该是有意义的,并且遵循 Go 语言的命名规范(驼峰命名法)。
  • parameter1 type1, parameter2 type2:参数列表,表示传递给函数的参数,包含参数的名称和类型,多个参数之间用逗号分隔。
  • returnTypeList:返回值类型,表示函数的返回类型。如果函数没有返回值,这部分可以省略。
  • {}:函数体,包含了函数的逻辑实现。
  • return 语句用于返回结果。如果函数有返回值,return 语句后面必须跟一个返回值。
go
package main
import "fmt"

// 定义一个简单的函数
func add(a int, b int) int {
    return a + b
}

func main() {
    result := add(3, 5) // 调用函数
    fmt.Println(result) // 输出:8
}

函数参数

函数可以接受零个或多个参数。参数可以是基本类型(如 int, string),也可以是复合类型(如 struct, slice)。

单一参数

可以给函数传递一个或多个参数,每个参数都需要指定类型。

go
func greet(name string) {
    fmt.Println("Hello", name)
}

多个参数

多个参数类型相同时,可以省略中间的类型声明,只在最后一次写类型:

go
// func add(a int, b int) int {
// a 和 b 都是 int 类型,因此可以简写为 a, b int
func add(a, b int) int {
    return a + b
}

可变参数

  • Go 支持可变参数(variadic parameters),也就是传递不定数量的参数。
  • 使用 ... 来表示可变参数,函数内部将这些参数作为切片(slice)处理。
go
// sum 函数接受任意数量的 int 类型参数,并将它们相加后返回总和
func sum(numbers ...int) int {
    total := 0
    for _, number := range numbers {
        total += number
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2, 3)) // 输出:6
    fmt.Println(sum(5, 10, 15, 20)) // 输出:50
}

函数返回值

函数可以返回零个或多个值。返回值的类型在函数定义时指定。

单一返回值

go
// multiply 函数接受两个 int 类型的参数 a 和 b,并返回它们的乘积
func multiply(a, b int) int {
    return a * b
}

// square 函数接受一个 int 类型的参数 x,并返回 x 的平方
func square(x int) int {
    return x * x
}

多返回值

  • Go 允许函数返回多个值,这在错误处理和函数返回多个结果时特别有用。
  • 多个返回值用逗号分隔,返回值的类型在函数定义时指定。
go
// divide 函数接受两个 int 类型的参数 a 和 b,并返回它们的商和余数 (int 类型)
func divide(a, b int) (int, int, error) {
    if b == 0 {
        return 0, 0, fmt.Errorf("division by zero")
    }
    quotient := a / b
    remainder := a % b
    return quotient, remainder, nil
}

func main() {
    quotient, remainder, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Quotient:", quotient, "Remainder:", remainder)
    }
}

命名返回值

函数的返回值可以被命名,使得返回值可以在函数体内作为变量使用,并且最后可以通过 return 直接返回,无需明确指定返回值。

go
// swap 函数接受两个 string 类型的参数 a 和 b,并返回两个 string 类型的值
// quotient 和 remainder 是命名返回值。在函数体内可以直接使用这两个变量,并且在 return 语句中可以省略返回值
func swap(a, b string) (x, y string) {
    x = b
    y = a
    return
}

// divide 函数接受两个 int 类型的参数 a 和 b,并返回两个 int 类型的值
func divide(a, b int) (quotient, remainder int, err error) {
    if b == 0 {
        return 0, 0, fmt.Errorf("division by zero")
    }
    quotient = a / b
    remainder = a % b
    return
}

func main() {
    fmt.Println(swap("hello", "world")) // 输出:world hello
    quotient, remainder, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Quotient:", quotient, "Remainder:", remainder)
    }
}

函数作为值和参数

  • 函数是一等公民(first-class citizen),这意味着函数可以作为值传递、赋值给变量、作为参数传递给其他函数,甚至可以作为返回值返回。

函数类型

函数可以定义为一种类型,这种类型可以用来声明变量、作为参数传递等。

go
// operation 是一个函数类型,它接受两个 int 类型的参数并返回一个 int 类型的值
type operation func(int, int) int

// add 和 multiply 函数符合这个类型,因此可以赋值给 op 变量
func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

func main() {
    var op operation
    op = add
    fmt.Println(op(3, 5))  // 输出:8

    op = multiply
    fmt.Println(op(3, 5))  // 输出:15
}

函数作为参数

go
type operation func(int, int) int

func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

// applyOperation 函数接受一个函数类型的参数 op,以及两个 int 类型的参数 a 和 b
// applyOperation 函数调用传递的函数 op,并返回结果
func applyOperation(op func(int, int) int, a, b int) int {
    return op(a, b)
}

func main() {
    result := applyOperation(add, 3, 5)
    fmt.Println(result)  // 输出:8

    result = applyOperation(multiply, 3, 5)
    fmt.Println(result)  // 输出:15
}
go
// apply 函数接受一个 operation 类型的参数 op,以及两个 int 类型的参数 a 和 b
// 调用传递的函数 op 并返回结果
func apply(op operation, a, b int) int {
    return op(a, b)
}

func main() {
    add := func(x, y int) int {
        return x + y
    }
    result := apply(add, 3, 5)
    fmt.Println(result) // 输出:8

    multiply := func(x, y int) int {
        return x * y
    }
    result = apply(multiply, 3, 5)
    fmt.Println(result) // 输出:15
}

函数作为返回值

go
type operation func(int, int) int

func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

// getOperation 函数根据传入的字符串参数返回不同的函数
func getOperation(op string) func(int, int) int {
    if op == "add" {
        return add
    } else if op == "multiply" {
        return multiply
    }
    return nil
}

func main() {
    // 调用 getOperation 函数获取相应的操作函数,并调用该函数
    op := getOperation("add")
    fmt.Println(op(3, 5))  // 输出:8

    op = getOperation("multiply")
    fmt.Println(op(3, 5))  // 输出:15
}
go
// multiplier 函数接受一个 int 类型的参数 factor,并返回一个函数
// 返回的函数接受一个 int 类型的参数 x,并返回 x 乘以 factor 的结果
func multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

func main() {
    double := multiplier(2)
    fmt.Println(double(5)) // 输出:10
}

匿名函数

  • 匿名函数(anonymous function)是没有名字的函数。
  • 它们可以作为表达式使用,赋值给变量,或作为参数传递给其他函数。
  • 匿名函数在 Go 语言中也可以形成闭包。
  • 匿名函数可以直接定义并使用,通常用于短小的函数逻辑。

基本匿名函数

go
func main() {
    // 定义并调用匿名函数
    func(name string) {
        fmt.Println("Hello", name)
    }("Go") // 输出:Hello Go

  func(a, b int) int {
    return a * b
	}(5, 6) // 输出:30
}

赋值给变量

go
func main() {
    greet := func(name string) {
        fmt.Println("Hello", name)
    }
    greet("Go") // 输出:Hello Go
}

闭包

闭包(closure)是指一个函数引用了其外部作用域中的变量,并且这个函数在外部作用域结束后仍然可以访问这些变量。

go
// adder 函数返回一个闭包。闭包捕获了 sum 变量,并在每次调用时累加 sum 的值
// 匿名函数引用了外部的 `sum` 变量,形成了闭包,能够在函数调用结束后继续访问并修改 `sum` 的值
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos := adder()
    fmt.Println(pos(1)) // 输出:1
    fmt.Println(pos(2)) // 输出:3
    fmt.Println(pos(3)) // 输出:6
}

递归函数

  • 函数可以调用自身,形成递归。
  • 递归函数通常需要一个基本条件来终止递归。
go
// 计算阶乘
func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}

func main() {
    fmt.Println(factorial(5)) // 输出:120
}

延迟调用 defer

  • defer 用于延迟函数的执行,通常在函数结束时执行。
  • 即使函数中途发生了 panicdefer 也会确保被调用。
  • 常用于清理操作,例如资源释放、关闭文件、释放资源等。
go
func main() {
    defer fmt.Println("World") // 在 main 函数返回之前执行
    fmt.Println("Hello")
}

// 输出:
// Hello
// World

错误处理

Go 语言的函数通常会返回一个错误(error)类型的值,用于表示函数执行过程中是否发生了错误。

go
// divide 函数在除数为零时返回一个错误。main 函数检查错误并进行相应的处理
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

方法与函数的区别

在 Go 中,方法(method)是带有接收者的函数,接收者可以是结构体或其他自定义类型。方法的定义与普通函数类似,但它附属于某个类型。

go
type Person struct {
    Name string
}

// 定义方法:greet 是 Person 类型的方法,接受者 p 是这个方法的调用对象
func (p Person) greet() {
    fmt.Println("Hello,", p.Name)
}

func main() {
    p := Person{Name: "Alice"}
    p.greet() // 输出:Hello, Alice
}
go
type Rectangle struct {
    width, height int
}

// 定义方法
func (r Rectangle) Area() int {
    return r.width * r.height
}

// 使用方法
rect := Rectangle{width: 10, height: 5}
area := rect.Area() // 返回 50

实验代码

go
package main

import (
	"fmt"
	"io/ioutil"
	"os"
)

// 从文件中读取内容
func readFile(filename string) (string, error) {
	// content, err := os.ReadFile(filename)
	// if err != nil {
	// 	return "", err
	// }

	file, err := os.Open(filename)
	if err != nil {
		// defer file.Close()
		_ = file.Close()
		return "", err
	}

	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func main() {

	// 匿名函数
	func(filename string) {
		content, err := readFile(filename)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(content)
	}("README.md")

	content, err := readFile("test.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(content)
}

// 输出:
// README
// open test.txt: The system cannot find the file specified.